﻿namespace ImageDupFinder
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
    using ImageDupFinder.ProgramCore;

    /// <summary>
    /// Thumbnail loader class
    /// </summary>
    public class ThumbLoader
    {
        /// <summary>
        /// Default thumbnail image width.
        /// </summary>
        public const int DefaultThumbnailWidth = 150;

        /// <summary>
        /// Default thumbnail image height.
        /// </summary>
        public const int DefaultThumbnailHeight = 100;

        /// <summary>
        /// Loader thread.
        /// </summary>
        private readonly BackgroundWorker loader;

        /// <summary>
        /// Items to load.
        /// </summary>
        private readonly Stack<ThumbLoaderItem> items;

        /// <summary>
        /// Initializes a new instance of the ThumbLoader class.
        /// </summary>
        public ThumbLoader()
        {
            this.items = new Stack<ThumbLoaderItem>();
            this.loader = new BackgroundWorker();
            this.loader.DoWork += new DoWorkEventHandler(this.Loader_DoWork);
        }

        /// <summary>
        /// Occurs when thumbnail image is loaded via asynchronous call.
        /// </summary>
        public event EventHandler<ThumbLoaderItem> ThumbnailLoadedCallback;

        /// <summary>
        /// Load thumbnail image in asynchronous way.
        /// </summary>
        /// <param name="item">Thumbnail to load</param>
        public void LoadAsync(ThumbLoaderItem item)
        {
            lock (this.items)
            {
                this.items.Push(item);
            }

            if (!this.loader.IsBusy)
            {
                this.loader.RunWorkerAsync();
            }
        }

        /// <summary>
        /// Generates thumbnail image.
        /// </summary>
        /// <param name="file">File path.</param>
        /// <param name="thumbnailWidth">Thumbnail image width.</param>
        /// <param name="thumbnailHeight">Thumbnail image height.</param>
        /// <returns>Thumbnail image</returns>
        public Image GenerateThumbnail(string file, int thumbnailWidth, int thumbnailHeight)
        {
            Image thumbnail=null;

            try
            {
                Bitmap image = (Bitmap)Bitmap.FromFile(file);

                // thubnail generation
                int w, h;
                if (image.Width > image.Height)
                {
                    w = thumbnailWidth;
                    h = image.Height * w / image.Width;
                }
                else
                {
                    h = thumbnailHeight;
                    w = image.Width * h / image.Height;
                }

                thumbnail = image.GetThumbnailImage(w, h, new Image.GetThumbnailImageAbort(this.ThumbAbortCallback), IntPtr.Zero);
                image.Dispose();
            }
            catch (OutOfMemoryException ome)
            {
                System.Diagnostics.Debug.Write(String.Format("File: {0}\nError: {1}",file, ome.ToString()));
            }
            catch (System.IO.FileNotFoundException fnfe)
            {
                System.Diagnostics.Debug.Write(String.Format("File: {0}\nError: {1}", file, fnfe.ToString()));
            }

            return thumbnail;
        }

        /// <summary>
        /// Generates thumbnail image using default thumbnail size.
        /// </summary>
        /// <param name="file">File path.</param>
        /// <returns>Thumbnail image.</returns>
        public Image GenerateThumbnail(string file)
        {
            return GenerateThumbnail(file, DefaultThumbnailWidth, DefaultThumbnailHeight);
        }

        /// <summary>
        /// Worker thread function.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void Loader_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            ThumbLoaderItem task;
            while (true)
            {
                lock (this.items)
                {
                    if (this.items.Count > 0)
                    {
                        task = this.items.Pop();
                        if (task == null)
                        {
                            return;
                        }
                    }
                    else
                    {
                        return;
                    }
                }

                // process task
                // load thumbnail
                task.ThumbnailImage = GenerateThumbnail(task.FilePath);

                // cross thread invocation
                if (this.ThumbnailLoadedCallback != null)
                {
                    object[] args = new object[] { this, task };
                    foreach (Delegate d in this.ThumbnailLoadedCallback.GetInvocationList())
                    {
                        ISynchronizeInvoke isi = d.Target as ISynchronizeInvoke;
                        if (isi != null && isi.InvokeRequired)
                        {
                            isi.BeginInvoke(d, args);
                        }
                        else
                        {
                            d.DynamicInvoke(args);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Required, but not used
        /// </summary>
        /// <returns>always true</returns>
        private bool ThumbAbortCallback()
        {
            return true;
        }
    }

    /// <summary>
    /// Class Representing thumbnail loader task item
    /// </summary>
    public class ThumbLoaderItem : EventArgs
    {
        /// <summary>
        /// File path.
        /// </summary>
        private readonly string filePath;

        /// <summary>
        /// Thumbnail image.
        /// </summary>
        private Image thumbnailImage = null;
      
        /// <summary>
        /// User data passed with event.
        /// </summary>
        private object userData;

        /// <summary> 
        /// Initializes a new instance of the ThumbLoaderItem class.
        /// Creates new thumbnail loader task item
        /// </summary>
        /// <param name="filePath">Image file path</param>
        /// <param name="userData">User data passed along</param>
        public ThumbLoaderItem(string filePath, object userData)
        {
            this.filePath = filePath;
            this.userData = userData;
        }

        /// <summary>
        /// Initializes a new instance of the ThumbLoaderItem class.
        /// Creates new thumbnail loader task item using provided ImageData.
        /// </summary>
        /// <param name="filePath">Image file path</param>
        public ThumbLoaderItem(string filePath)
            : this(filePath, null)
        {
        }

        /// <summary>
        /// Gets file path of orginal image.
        /// </summary>
        public string FilePath
        {
            get { return filePath; }
        } 

        /// <summary>
        /// Gets or sets generated thumbnail image.
        /// </summary>
        public Image ThumbnailImage
        {
            get { return thumbnailImage; }
            set { thumbnailImage = value; }
        }

        /// <summary>
        /// Gets or sets user data passed with event
        /// </summary>
        public object UserData
        {
            get { return this.userData; }
            set { this.userData = value; }
        }
    }
}
